﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace IntelliLance.Math.Parser
{
    /// <summary>
    /// Translates a token collection into an expression.
    /// </summary>
    /// <remarks>
    /// Use this class after parsing a mathematical expression string using the MathParser class.
    /// This class uses the Postfix Algorithm to convert the input tokens collection to a System.Linq.Expressions.LambdaExpression instance.
    /// Use the Transform method to return  an expression, or use the TransformAndCompile method to return a Delegate instance that's 
    /// equivalent to the parsed expression.
    /// </remarks>
    public class ExpressionTransform 
    {
        #region Fields
        private Queue<Token> _tokens;
        #endregion  

        #region Methods
        
        public ExpressionTransform(Queue<Token> tokens)
        {
            if (tokens == null)
                ThrowHelpers.ThrowNullArgument("tokens");

            if (tokens.Count == 0)
                ThrowHelpers.ThrowEmptyCollection("tokens");

            _tokens = tokens;
        }

        public LambdaExpression Transform()
        { 
            Stack<Expression> expressions;
            List<ParameterExpression> @params;
            var ex = TransformPostfix(out expressions, out @params);
            return Expression.Lambda(ex, @params);
        }

        public Delegate TransformAndCompile()
        {
            return Transform().Compile();
        }

        private Expression TransformPostfix(out Stack<Expression> expressions, out List<ParameterExpression> @params)
        {
            expressions = new Stack<Expression>();
            @params = new List<ParameterExpression>();
            while (CheckLenght() && _tokens.Peek() != null)
            {
                var current = _tokens.Dequeue();
                var v = current as VariableToken;
                if (v != null)
                {
                    var ex = ExpressionVisitor.Variable(v);
                    expressions.Push(ex);
                    @params.Add(ex);
                    continue;
                }

                var c = current as ConstantToken;
                if (c != null)
                {
                    var ex1 = ExpressionVisitor.Constant(c);
                    expressions.Push(ex1);
                    continue;

                }
                var f = current as FunctionToken;
                if (f != null)
                {
                    int counter = 0;
                    var l = new List<Expression>();
                    while (expressions.Count > 0 && counter < f.ParametersCount)
                    {
                        var lastItem = expressions.Pop();
                        l.Add(lastItem);
                        counter++;
                    }
                    var func = ExpressionVisitor.Call(f, l);
                    expressions.Push(func);
                }

                var o = current as OperatorToken;
                if (o != null)
                {
                    Expression opCall = null;
                    if (o.IsBinary)
                    {
                        var right = expressions.Pop();
                        var left = expressions.Pop();

                        opCall = ExpressionVisitor.BinaryExpression(o, right, left);
                    }
                    else
                    { 
                        var operand = expressions.Pop();
                        opCall = ExpressionVisitor.UnaryExpression(o, operand);

                    }
                   expressions.Push(opCall);
                }
            }
            return expressions.Pop();
        }

        private bool CheckLenght()
        {
            return _tokens.Count > 0;
        }    
        #endregion

    }
}
